本文为看雪论坛优秀文章
看雪论坛作者ID:xwtwho
最近看了下WhatsApp (android及pc版本),实现了协议发送私信(模板信息也可以发), 记录下学习过程。
LineageOs 17.1 (android 10)
刚开始了解了下WhatsApp,说是消息端对端加密的,信息只能双方解密,服务器都不知道的,初始看了一脸懵,在网上找了下资料:【翻译】WhatsApp 加密概述(技术白皮书)http://www.caotama.com/1993224.htmlhttps://blog.csdn.net/BMW33939/article/details/120322512https://blog.csdn.net/yzpbright/article/details/117808556不过看这些资料其实有点尴尬,刚开始不知道的时候看这些也看不懂,各种密钥概念,加密过程直接绕晕了,等开始分析app,能看懂的时候,发现也搞完了,回过头来看,确实上面写的(特别是白皮书)都是对的,只是初始不了解的时候理解不了,毕竟上面文章不是实操流程。首先看下数据流,确定下网络传输方式,结合Wireshark,通过hook及下断点等方式确定了是TCP:查了下IP:157.240.199.61香港 Facebook知道发送点后,再结合JNI函数(根据名称就可以确定重要的模块libwhatsapp.so,libcurve25519.so),逐步确定调用线。Curve25519 是目前最高水平的 Diffie-Hellman 函数,适用于广泛的场景,由 Daniel J. Bernstein 教授设计。在密码学中,Curve25519 是一个椭圆曲线提供 128 位安全性,设计用于椭圆曲线 Diffie-Hellman(ECDH)密钥协商方案。它是最快的 ECC 曲线之一,并未被任何已知专利所涵盖。libcurve25519.so boolean org.whispersystems.curve25519.NativeCurve25519Provider.smokeCheck(int) 0x9d782548 func: 0x78b8406288 0x0 iOffset: 4288libcurve25519.so byte[] org.whispersystems.curve25519.NativeCurve25519Provider.generatePrivateKey(byte[]) 0x9d782638 func: 0x78b8405a2c 0x0 iOffset: 3a2clibcurve25519.so byte[] org.whispersystems.curve25519.NativeCurve25519Provider.calculateAgreement(byte[], byte[]) 0x9d7825c0 func: 0x78b8405b68 0x0 iOffset: 3b68 at org.whispersystems.curve25519.NativeCurve25519Provider.generatePublicKey(Native Method) at org.whispersystems.curve25519.OpportunisticCurve25519Provider.generatePublicKey(:750206)[MI 10::com.whatsapp]-> byteArray,byte src : [10,2,49,49] protobuf格式 at X.1FH.A02(Native Method) at com.whatsapp.jobqueue.job.SendE2EMessageJob.writeObject(:271863) at java.lang.reflect.Method.invoke(Native Method)java.security.MessageDigest可以直接hook了看数据流的变化,这个时候对加密模式就有了一定了解。客户端跟服务器有一个加密方式AES-256-GCM,每个包的加密IV都不同,如果发送的数据包是私信内容的,那里面的私信内容是第二层的加密(aes-256-cbc),这一层的数据因为key的生成用到了对方的公钥做DH得到,所以只能接收方才能解密,每条私信内容加密的key也是不同的。每次打开app都会重新发起TCP连接(已经是用验证码登录的情况,后续的打开app),这个时候要初始化一对密钥,公钥会在连接建立后的第一个发送包中包含,发给服务器。这里还会用到其它几种密钥(自己的identity_key,标记登录会话类似抖音session token的key,服务器的公钥),这些key相互组合通过calculateAgreement及HKDF扩展得到中间数据和密钥,包括用来加密下面的数据。这个数据校验通过后,就是连接正常建立了,后面就可以发送私信了。前面提到,私信内容的加密其实又是一种加密模式,这个数据是发送者和接收者交互用的,服务器也解密不了的,它只是转发加密后的数据。私信内容加密是aes-256-cbc,这里会用到消息密钥(Message Key), 80 个字节的值,用于加密消息内容。32 个字节用于 AES-256 密钥,32 个字节用于 HMAC-SHA256 密钥,16 个字节用于 IV。HMAC-SHA256密钥用于计算私信内容aes加密的结果的消息认证码,只取结果的前8字节。这个消息密钥(Message Key)的计算涉及到一个棘轮变换,每加密一条消息后,就要通过sha256的组合算法计算下一条消息加密用的Message Key了。私信消息的发送,有个会话的概念,双方建立端对端通信需要建立一个会话,就是构造约定好密钥,建立会话后,双方就可以保存这个相关环境参数,直接按消息序号就可以根据当前的密钥计算出要加解密的Message Key,用于加解密消息了。就算之后重新打开app,私信内容的加解密也可以继续按之前的会话继续,不用重新建立会话。所以刚开始分析的时候,为了简单,就是在app会话建立的基础上分析的,这样只用hook拿到当前的消息的Message Key,就可以计算出指定消息序号的Message Key,直接加密信息发送就可以了。会话发起人为接收人申请身份公钥(public Identity Key)、已签名的预共享公钥(public Signed Pre Key)和一个一次性预共享密钥(One-Time Pre Key)。服务器返回所请求的公钥。一次性预共享密钥(One-Time Pre Key)仅使用一次,因此请求完成后将从服务器删除。如果一次性预共享密钥(One-Time Pre Key)被用完且尚未补充,则返回空。发起人将接收人的身份密钥(Identity Key)存为 Irecipient,将已签名的预共享密钥(Signed Pre Key)存为 Srecipient,将一次性预共享密钥(One-Time Pre Key)存为 Orecipient。发起者生成一个临时的 Curve25519 密钥对 Einitiator发起者加载自己的身份密钥(Identity Key)作为 Iinitiator发起者计算主密钥 master_secret = ECDH ( Iinitiator, Srecipient ) || ECDH ( Einitiator, Irecipient ) || ECDH ( Einitiator, Srecipient ) || ECDH ( Einitiator, Orecipient ) 。如果没有一次性预共享密钥(One-Time Pre Key),最终 ECDH 将被忽略。发起者使用 HKDF 算法从 master_secret 创建一个根密钥(Root Key)和链密钥(Chain Keys)。过程是这样,但是这个要实际跟一遍,才能比较了解,涉及到的算法本身不复杂,主要是这些key的变换流程。对建立会话,初始还要创建2组密钥对(OurBaseKey和ourRatchetKey):按流程实现后测试,发送信息后对方能收到,但是看不到内容:
查了下,网上有说是发送方换设备了就可能这样,那我这个肯定不属于这个情况。当时一直没搞定,开始认为是加密算法还原有问题,反复核对了几遍,按照hook的数据及参数加密结果跟实际的完全一致。反复的核对这种数据,搞得没脾气了,后来就静下心来想了下,要实现这个端对端加密应该怎么做,最后发现一个不确定的点,就是oneTimePreKey,这个请求的时候,服务器直接返回了一个对方的一次性预共享密钥,这个接收方是提供了一批存到服务器的,用一个就删除一个,如果会话最后没建立成功,服务器应该也不会同步给接收方的,并且实际发送建立会话的数据中也没看到有回发这个过去,那接收方要能正确解密,肯定要能知道当前会话用的是哪个一次性预共享密钥。后来想到应该是有个ID来标识的,当时也确实发现发送建立会话的数据中有几个字段值不确定,但是从返回的接收方key数据和发送的私信数据中没找到这个共同的值(后来发现其实是数据格式不同)。一直没解决,就新开了条战线,分析PC版本,希望能找出这个差异点,解决这个问题。PC上的WhatsApp 7个进程,也是首先找收发数据点,最后确定下面的进程是处理网络数据的,根据命令行参数也可以看出来:
跟踪发现PC上发送是用的websocket,直接下载ssl源码,参考标出函数:跟踪的时候发现,接收的数据没有进一步的逻辑处理,有时候就请求清空内存了,就猜测可能是发到其它进程去了,看到有MojoMessages相关的。查了下:Mojom是chromium最新的跨平台进程通信框架。关注点不在这,没细看。最后发现,这个进程只是处理网络层的,具体的私信逻辑在另一个进程:后面就是顺着调用跟踪加密流程了,弄清楚后,可以直接上frida hook PC数据(注入dll也行):
顺便提一下,第一次拿码登录流程是走的http模式,数据加密也会用到密钥对的生成。
学习了端对端通信实现,对这种加密模式有了一定了解。
熟悉了Curve25519应用。
看雪ID:xwtwho
https://bbs.pediy.com/user-home-44250.htm
*本文由看雪论坛 xwtwho 原创,转载请注明来自看雪社区